import requests
import os
import time
import random
import re
import concurrent.futures
from urllib.parse import urlparse
import threading
import pandas as pd
import requests

# 全局变量
_current_proxy_index = 0
proxy_lock = threading.Lock()  # 添加线程锁以确保代理池操作的线程安全
progress_lock = threading.Lock()  # 添加线程锁以确保进度显示的线程安全
processed_count = 0
failed_count = 0

# 获取脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
excel_file = os.path.join(script_dir, '店铺.xlsx')
output_dir = os.path.join(script_dir, 'url')

# 代理API URL
PROXY_API_URL = 'http://dddip.top/api/get?token=426e97524922b118745249567bd332&number=1000&type=https&format=1'
proxies_pool = []

# 获取下一个代理的函数（线程安全）
def get_next_proxy():
    global _current_proxy_index
    with proxy_lock:
        if len(proxies_pool) > 0:
            proxy = proxies_pool[_current_proxy_index]
            _current_proxy_index = (_current_proxy_index + 1) % len(proxies_pool)
            return proxy
        else:
            return None

# 确保输出目录存在
def ensure_output_dir():
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

# 从API获取代理IP列表
def get_proxies_from_api():
    try:
        print(f"正在从API获取代理IP...")
        response = requests.get(PROXY_API_URL, timeout=10)
        response.raise_for_status()
        
        # 解析API返回的代理IP列表
        proxies_text = response.text.strip()
        proxies_list = proxies_text.split('\r\n')
        
        # 去重处理
        original_count = len(proxies_list)
        proxies_list = list(set(proxies_list))  # 使用集合去重
        unique_count = len(proxies_list)
        if original_count > unique_count:
            print(f"已去除{original_count - unique_count}个重复代理IP")
        
        # 格式化代理IP为requests可用的格式
        # 注意：即使是HTTPS请求，也使用HTTP代理格式，这是处理SSL错误的推荐方式
        formatted_proxies = []
        for proxy in proxies_list:
            if proxy.strip():
                formatted_proxies.append({
                    'http': f'http://{proxy.strip()}',
                    'https': f'http://{proxy.strip()}'  # HTTPS请求也使用HTTP代理
                })
        
        print(f"成功获取{len(formatted_proxies)}个代理IP")
        return formatted_proxies
    except Exception as e:
        print(f"获取代理IP失败: {str(e)}")
        # 添加备用代理获取逻辑
        print("使用备用代理获取方法...")
        # 这里可以添加备用代理API或使用固定代理
        return []

# 初始化代理池
def init_proxy_pool():
    global proxies_pool
    proxies_pool = get_proxies_from_api()
    
    # 如果没有获取到代理，使用空代理（直接请求）
    if not proxies_pool:
        proxies_pool = [None]

# 核心爬取函数（每个线程调用的函数）
def crawl_url(row_data, total_rows):
    global processed_count, failed_count, proxies_pool
    
    index, row = row_data
    
    try:
        # 获取第一列作为文件名（店铺名）
        shop_name = str(row.iloc[0]).strip()
        # 获取第二列作为网址
        url = str(row.iloc[1]).strip()
        
        # 跳过无效数据
        if not shop_name or not url or url.lower() == 'nan':
            with progress_lock:
                print(f"跳过行：无效的店铺名或网址")
            return False
        
        # 倒序处理时，计算正确的进度显示
        reversed_index = total_rows - index
        with progress_lock:
            print(f"正在处理倒序第{reversed_index}/{total_rows}条：{shop_name} - {url}")
        
        # 预先计算文件名并检查文件是否存在
        # 清理文件名，移除非法字符
        valid_filename = "".join([c for c in shop_name if c.isalnum() or c in (' ', '_', '-', '.')])
        valid_filename = valid_filename.replace(' ', '_')
        
        # 添加前置0，确保文件名长度为5位数
        # 提取数字部分并填充
        digits = ''.join(filter(str.isdigit, valid_filename))
        if digits:
            padded_digits = digits.zfill(5)
            valid_filename = valid_filename.replace(digits, padded_digits)
        
        # 计算完整文件路径
        file_path = os.path.join(output_dir, f"{valid_filename}.txt")
        
        # 检查文件是否已存在，如果存在则跳过
        if os.path.exists(file_path):
            with progress_lock:
                print(f"{reversed_index}:文件已存在，跳过读取：{file_path}")
            return False
        
        # 文件不存在，继续处理
        # 发送请求获取网页源码
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
        
        # 确保URL格式正确并清理非法字符
        # 移除URL中的特殊字符和非法部分
        url = re.sub(r'[^a-zA-Z0-9:/._-]', '', url)
        if not url.startswith(('http://', 'https://')):
            url = 'http://' + url
        
        # 获取下一个可用代理
        proxy = get_next_proxy()
        
        # 发送请求，设置超时时间为10秒，使用代理
        # 增加重试机制和直接请求备选
        max_retries = 3
        retry_count = 0
        success = False
        
        while retry_count < max_retries and not success:
            try:
                # 根据重试次数调整超时时间
                timeout = 15 if retry_count == 0 else 12 if retry_count == 1 else 10
                
                if retry_count == 0 and proxy:
                    with progress_lock:
                        # 确保代理是有效的字典格式
                        if isinstance(proxy, dict) and 'http' in proxy:
                            print(f"{reversed_index}:使用代理: {proxy['http']}")
                            response = requests.get(url, headers=headers, proxies=proxy, timeout=timeout)
                        else:
                            # 如果代理格式无效，使用直接请求
                            print(f"{reversed_index}:代理格式无效，使用直接请求")
                            response = requests.get(url, headers=headers, timeout=timeout)
                else:
                    # 如果是重试，或没有可用代理，尝试直接请求
                    if retry_count > 0:
                        with progress_lock:
                            print(f"{reversed_index}:第{retry_count+1}次尝试，使用直接请求")
                    else:
                        with progress_lock:
                            print(f"{reversed_index}:无可用代理，使用直接请求")
                    response = requests.get(url, headers=headers, timeout=timeout)
                response.raise_for_status()  # 如果状态码不是200，抛出异常
                success = True
            except requests.exceptions.RequestException as e:
                retry_count += 1
                error_type = type(e).__name__
                
                with progress_lock:
                    if retry_count == 0 and proxy:
                        print(f"{reversed_index}:代理请求失败({error_type}: {str(e)}，尝试移除代理并更换")
                    else:
                        print(f"{reversed_index}:请求失败({error_type}: {str(e)})，尝试第{retry_count+1}次")
                
                # 统一处理代理移除逻辑 - 根据错误类型决定是否移除代理
                proxy_removed = False
                with proxy_lock:
                    # 对于ProxyError和ReadTimeout错误，移除代理
                    if retry_count == 0 and proxy and proxy in proxies_pool and error_type in ['ProxyError', 'ReadTimeout']:
                        proxies_pool.remove(proxy)
                        proxy_removed = True
                        with progress_lock:
                            # 确保代理是有效的字典格式
                            if isinstance(proxy, dict) and 'http' in proxy:
                                print(f"{reversed_index}:从代理池中移除不可用代理: {proxy['http']} (错误类型: {error_type})\n代理池剩余数量: {len(proxies_pool)}")
                            else:
                                print(f"{reversed_index}:从代理池中移除不可用代理 (错误类型: {error_type})\n代理池剩余数量: {len(proxies_pool)}")
                        
                    # 当代理池数量少于一定阈值时，尝试补充代理
                    if proxies_pool and len(proxies_pool) < 10:
                        with progress_lock:
                            print(f"{reversed_index}:代理池数量不足，开始补充代理...")
                        new_proxies = get_proxies_from_api()
                        if new_proxies:
                            with progress_lock:
                                print(f"{reversed_index}:成功补充 {len(new_proxies)} 个代理")
                        else:
                            with progress_lock:
                                print(f"{reversed_index}:补充代理失败，继续使用现有代理")
                    # 如果代理池为空，尝试重新获取代理
                    elif not proxies_pool:
                        proxies_pool = get_proxies_from_api()
                
                # 获取新的代理
                if proxy_removed or not proxy:
                    proxy = get_next_proxy()
                    # 如果仍然没有代理，尝试重新初始化代理池
                    if not proxy:
                        with proxy_lock:
                            if not proxies_pool:
                                proxies_pool = get_proxies_from_api()
                                if not proxies_pool:
                                    proxies_pool = [None]
                        proxy = get_next_proxy()
                        # 如果还是没有代理，等待一段时间再重试
                        if not proxy:
                            with progress_lock:
                                print(f"{reversed_index}:代理池为空，等待5秒后重试...")
                            time.sleep(5)
                
                # 如果重试次数已用完，抛出异常
                if retry_count >= max_retries:
                    raise
        
        # 获取网页源码
        html_content = response.text
        
        # 检查网页内容是否包含"义乌购-登录"文本
        login_text_count = 0
        max_login_retries = 2  # 遇到登录页面的最大重试次数
        
        while "义乌购-登录" in html_content and login_text_count < max_login_retries:
            login_text_count += 1
            with progress_lock:
                print(f"{reversed_index}:检测到\"义乌购-登录\"文本 (第{login_text_count}次)，尝试使用新代理重新获取")
            
            # 线程安全地移除代理
            with proxy_lock:
                if proxy and proxy in proxies_pool:
                    proxies_pool.remove(proxy)
                    with progress_lock:
                        # 确保代理是有效的字典格式
                        if isinstance(proxy, dict) and 'http' in proxy:
                            print(f"{reversed_index}:移除不可用代理: {proxy['http']} (检测到登录页面)")
                        else:
                            print(f"{reversed_index}:移除不可用代理 (检测到登录页面)")
                    
                # 当代理池数量少于一定阈值或为空时，尝试补充代理
                if not proxies_pool or len(proxies_pool) < 10:
                    with progress_lock:
                        print(f"{reversed_index}:代理池数量不足，开始补充代理...")
                    new_proxies = get_proxies_from_api()
                    if new_proxies:
                        with progress_lock:
                            print(f"{reversed_index}:成功补充 {len(new_proxies)} 个代理")
                    else:
                        with progress_lock:
                            print(f"{reversed_index}:补充代理失败，使用现有代理")
                        if not proxies_pool:
                            proxies_pool = [None]
            
            # 获取下一个代理并重新请求
            proxy = get_next_proxy()
            
            # 使用新代理重新请求
            try:
                # 增加等待时间，避免请求过于频繁
                time.sleep(random.uniform(2, 4))
                
                timeout = 15  # 对登录页面的重试使用更长的超时时间
                if proxy:
                    with progress_lock:
                        # 确保代理是有效的字典格式
                        if isinstance(proxy, dict) and 'http' in proxy:
                            print(f"{reversed_index}:使用新代理: {proxy['http']} 重新请求")
                            response = requests.get(url, headers=headers, proxies=proxy, timeout=timeout)
                        else:
                            # 如果代理格式无效，使用直接请求
                            print(f"{reversed_index}:新代理格式无效，使用直接请求")
                            response = requests.get(url, headers=headers, timeout=timeout)
                else:
                    with progress_lock:
                        print(f"{reversed_index}:无可用代理，使用直接请求重新尝试")
                    response = requests.get(url, headers=headers, timeout=timeout)
                response.raise_for_status()
                html_content = response.text
                with progress_lock:
                    print(f"{reversed_index}:使用新代理重新请求成功")
            except Exception as e:
                with progress_lock:
                    print(f"{reversed_index}:使用新代理重新请求失败: {str(e)}")
                # 如果重试次数已用完，保留原始响应内容，继续处理
                if login_text_count >= max_login_retries:
                    break
        
        if "义乌购-登录" not in html_content:
            # 保存源码到文件
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(html_content)
            
            with progress_lock:
                print(f"{reversed_index}:成功保存：{file_path}")
                processed_count += 1
        
        # 添加随机延迟，避免请求过于规律
        time.sleep(random.uniform(1, 3))  # 增加延迟时间，减少请求频率
        
        return True
    except requests.exceptions.RequestException as e:
        with progress_lock:
            print(f"{reversed_index}:请求失败 ({shop_name} - {url})：{str(e)}")
            failed_count += 1
        return False
    except Exception as e:
        with progress_lock:
            print(f"{reversed_index}:处理失败 ({shop_name} - {url})：{str(e)}")
            failed_count += 1
        return False

# 主函数
def main():
    # 确保输出目录存在
    ensure_output_dir()
    
    # 初始化代理池
    init_proxy_pool()
    
    # 读取Excel文件内容
    try:
        # 使用pandas读取Excel文件
        df = pd.read_excel(excel_file)
        # 取前30条数据测试
        df = df.head(20)
        
        # 显示前几行数据，确认列的顺序
        print("Excel文件内容预览:")
        print(df.head())
        
        # 获取总行数
        total_rows = len(df)
        print(f"共发现{total_rows}条记录")
        
        # 准备要处理的数据（倒序）
        data_to_process = list(df.iloc[::-1].iterrows())
        
        # 配置线程池大小
        # 注意：线程数不宜过大，避免请求过于频繁导致被封IP
        thread_count = 5  # 进一步降低线程数，减少请求频率
        
        print(f"使用{thread_count}个线程进行多线程爬取...")
        start_time = time.time()
        
        # 初始化计数器
        global processed_count, failed_count
        processed_count = 0
        failed_count = 0
        
        # 使用线程池并行处理
        with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
            # 创建一个字典，存储future对象和对应的行索引
            future_to_row = {executor.submit(crawl_url, row_data, total_rows): row_data 
                            for row_data in data_to_process}
                
            # 等待所有任务完成
            for future in concurrent.futures.as_completed(future_to_row):
                row_data = future_to_row[future]
                try:
                    future.result()
                except Exception as e:
                    index, row = row_data
                    shop_name = str(row.iloc[0]).strip() if len(row) > 0 else "未知店铺"
                    url = str(row.iloc[1]).strip() if len(row) > 1 else "未知URL"
                    with progress_lock:
                        print(f"任务执行异常 ({shop_name} - {url})：{str(e)}")
                        failed_count += 1
        
        end_time = time.time()
        elapsed_time = end_time - start_time
        
        with progress_lock:
            print(f"\n所有店铺网址爬取完成！")
            print(f"总耗时: {elapsed_time:.2f}秒")
            print(f"成功处理: {processed_count}条")
            print(f"处理失败: {failed_count}条")
            
    except Exception as e:
        print(f"读取Excel文件失败：{str(e)}")
        print("请检查文件是否存在，以及是否安装了pandas和openpyxl库。")
        print("安装命令：pip install pandas openpyxl requests")
        # 确保即使在异常情况下也能正确报告计数
        print(f"成功处理: {processed_count}条")
        print(f"处理失败: {failed_count}条")

if __name__ == "__main__":
    main()